/*
 * This file is a part of ShtutXML
 * © Barak Itkin <lightningismyname at gmail dot com>, 2011
 *
 * ShtutXML is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.shtutxml;

import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * A class for representing a Text-Node of a DOM (XML) document. This class also
 * adds an additional doubly-linked-list functionallity and it is used for
 * creating a list of all the text nodes in the document
 */
public class TextSection {

    public TextSection prev, next;
    public Node textNode;

    /**
     * Create a new text section. This method DOES NOT CONNECT the previous
     * section with this one. For doing that, use {@link InsertAfterMe}
     */
    public TextSection (TextSection prev, Node textNode) {
        this.prev = prev;

        this.next = null;
        this.textNode = textNode;
    }

    public String getTextContent() throws DOMException {
        return textNode.getTextContent();
    }

    public Node getParentNode() {
        return textNode.getParentNode();
    }

    /**
     * Check if this node is the last Text-Node inside it's parent. This method
     * call assumes that all the TextSection objects were initialized correctly,
     * like StrXML does.
     */
    public boolean isNodeEnd() {
        if (next == null) {
            return true;
        }

        short position = getParentNode().compareDocumentPosition(next.getParentNode());

        if (position == Node.DOCUMENT_POSITION_CONTAINED_BY
                || position == Node.DOCUMENT_POSITION_DISCONNECTED
                || position == Node.DOCUMENT_POSITION_PRECEDING) {
            return true;
        } else if (position == Node.DOCUMENT_POSITION_CONTAINS) {
            return false;
        } else // if (position == Node.DOCUMENT_POSITION_FOLLOWING
        // || position == Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC)
        {
            throw new AssertionError("Bad position relation!");
        }
    }

    /**
     * Check if this node is the first Text-Node inside it's parent. This method
     * call assumes that all the TextSection objects were initialized correctly,
     * like StrXML does.
     */
    public boolean isNodeBegin() {
        if (prev == null) {
            return true;
        } else {
            return prev.isNodeEnd();
        }
    }

    /**
     * Insert a TextSection object after the current one
     * @param newNext The TextSection to insert after the current one
     */
    public void InsertAfterMe(TextSection newNext) {
        TextSection oldNext = this.next;

        this.next = newNext;
        newNext.prev = this;

        newNext.next = oldNext;
        if (oldNext != null) {
            oldNext.prev = newNext;
        }
    }



    static boolean isChildOrSame (Node parent, Node child) {
        while (child != null) {
            if (child.isSameNode(parent))
                return true;
            else
                child = child.getParentNode();
        }
        return false;
    }


    /**
     * Split the current node in 2 pieces. The new node will be added
     * directly after this one (and a pointer to it will be returned).
     *
     * This should not be used directly! Use only through the interface provided
     * by StrXML!!!
     * @param index where to split the text
     * @return the next node
     */
    TextSection splitSection (int index) {
        /* Create a new text node for the second half of the text */
        Node nextTextNode = textNode.cloneNode(false);
        nextTextNode.setTextContent(getTextContent().substring(index));

        /* Now trim the text in the current node to the first half */
        textNode.setNodeValue(getTextContent().substring(0, index));

        /* Finally, add that text node after the current one */
        Node parent = getParentNode();
        parent.insertBefore(nextTextNode, textNode.getNextSibling());

        /* Now, create an apropriate text section object */
        TextSection theNext = new TextSection (this, nextTextNode);

        this.InsertAfterMe(theNext);

        return theNext;
    }


    /**
     * @param index
     * @param lastSplitOnChild
     * @return the second part of the textsection as created by the split
     */
    public TextSection splitSectionUpNotIncluding (int index, Node lastSplitOnChild)
    {
        if (! isChildOrSame (lastSplitOnChild, textNode))
            throw new IllegalArgumentException ("Not really a parent node!");
        Node childIter = lastSplitOnChild.getFirstChild();
        while (! isChildOrSame (childIter, textNode)) {
            childIter = childIter.getNextSibling();
        }
        return splitSectionUp (index, childIter);
    }
    /**
     * Split the current node in 2 pieces, and also split all the parents up to
     * a specified parent (including that parent). The new text node will be
     * added directly after this one (and a pointer to it will be returned).
     * @param index where to split the text
     * @return the next node
     */
    public TextSection splitSectionUp (int index, Node lastSplitHere) {
        if (! isChildOrSame (lastSplitHere, textNode))
            throw new IllegalArgumentException ("Not really a parent node!");

        Node cloneSRC = lastSplitHere.cloneNode(false);
        lastSplitHere.getParentNode().insertBefore(cloneSRC, lastSplitHere.getNextSibling());

        Node cloneIter = cloneSRC;
        Node sourceIter = lastSplitHere;

        while (sourceIter.hasChildNodes()) {
            Node childIter = sourceIter.getFirstChild(), nextChild;
            boolean beforeFound = true;

            /* Find the parent of the text node */
            while (! isChildOrSame (childIter, textNode)) {
                childIter = childIter.getNextSibling();
            }

            /* childIter is now the parent of the text node! */

            /* Add the parent to the clone */
            cloneIter.appendChild(childIter.cloneNode(false));

            /* Add everything after it to the "clone" and remove from the source */
            while ((nextChild = childIter.getNextSibling()) != null) {
                cloneIter.appendChild(sourceIter.removeChild (nextChild));
            }

            /* Now, we have the last child of the source node as a parent of
             * the text node, and same for the clone. So progress more downwards
             */
            sourceIter = childIter;
            cloneIter = cloneIter.getFirstChild();
        }

        if (! sourceIter.isSameNode(textNode))
            throw new AssertionError();

        /* When we finished splitting the parents, we should now be at the
         * actual text nodes. Note that none of the text sections was
         * invalidated untill now!
         */
        TextSection theNext = new TextSection (this, cloneIter);
        cloneIter.setTextContent(cloneIter.getTextContent().substring(index));
        sourceIter.setTextContent(sourceIter.getTextContent().substring(0,index));

        this.InsertAfterMe(theNext);

        return theNext;
    }

    public static void wrapWith (Node child, Node wrapper) {
        Node parent = child.getParentNode ();
        parent.insertBefore (wrapper, child);
        parent.removeChild (child);
        wrapper.appendChild (child);
    }

    public void wrapWith (Node wrapper) {
        wrapWith (textNode, wrapper);
    }

    @Override
    public String toString() {
        return getTextContent() + " (child of " + getParentNode() + ")";
    }


}
